from django.db import models
from django.db.models import Sum
from django.utils.html import escape
from django.utils.text import capfirst
from django.core.exceptions import ObjectDoesNotExist
from a115.utils.mathutils import randint
from django.conf import settings
from django.contrib.auth.models import User, Group
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe

__all__ = ["ObjectDoesNotExist", "fst", "rnd", "get_deleted_objects", "deleted_objects_list", "cpy", "copy_model_instance", "settings", "User", "Group", "Sum", ]

fst_qs = lambda qs: qs[:1][0] if qs.count() else None

def rnd_qs(qs): 
    n = qs.count()
    return qs.all()[randint(0, n-1)] if n else None

""" Return the first found instance of a model or None """
def fst(M, **kwargs):
    order_by = kwargs.pop("order_by", None)
    qs = M.objects.filter(**kwargs)
    if order_by: qs = qs.order_by(order_by)
    return fst_qs(qs)

""" Return a random instance of a model or None """
rnd = lambda M, **kwargs: rnd_qs(M.objects.filter(**kwargs))

def _nest_help(obj, depth, val):
    current = obj
    for i in range(depth):
        current = current[-1]
    current.append(val)

def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth):
    """
    Helper function that recursively populates deleted_objects.

    `levels_to_root` defines the number of directories (../) to reach the
    admin root path. In a change_view this is 4, in a change_list view 2.

    This is for backwards compatibility since the options.delete_selected
    method uses this function also from a change_list view.
    This will not be used if we can reverse the URL.
    """
    nh = _nest_help # Bind to local variable for performance
    if current_depth > 16:
        return # Avoid recursing too deep.
    opts_seen = []
    for related in opts.get_all_related_objects():
        if related.opts in opts_seen:
            continue
        opts_seen.append(related.opts)
        rel_opts_name = related.get_accessor_name()
        if isinstance(related.field.rel, models.OneToOneRel):
            try:
                sub_obj = getattr(obj, rel_opts_name)
            #except ObjectDoesNotExist:
            except:
                pass
            else:
                nh(deleted_objects, current_depth,
                    [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
                get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
        else:
            has_related_objs = False
            for sub_obj in getattr(obj, rel_opts_name).all():
                has_related_objs = True
                nh(deleted_objects, current_depth, [u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
                get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2)
    for related in opts.get_all_related_many_to_many_objects():
        if related.opts in opts_seen:
            continue
        opts_seen.append(related.opts)
        rel_opts_name = related.get_accessor_name()
        has_related_objs = False

        # related.get_accessor_name() could return None for symmetrical relationships
        if rel_opts_name:
            rel_objs = getattr(obj, rel_opts_name, None)
            if rel_objs:
                has_related_objs = True

        if has_related_objs:
            for sub_obj in rel_objs.all():
                nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])

def deleted_objects_list(obj, user):
    deleted_objects = [mark_safe(u'%s: %s' % (escape(force_unicode(capfirst(obj._meta.verbose_name))), escape(obj))), []]
    perms = set()
    get_deleted_objects(deleted_objects, perms, user, obj, obj._meta, 1)
    return deleted_objects

def cpy(obj):
    temporary_copy = dict([(f.name,getattr(obj, f.name)) for f in obj._meta._fields()])
    temporary_copy.pop('id', None)
    return obj.__class__(**temporary_copy)
    
def copy_model_instance(obj):     
    """Create a copy of a model instance """  
    from django.db.models import AutoField
    initial = dict([(f.name, getattr(obj, f.name)) for f in obj._meta.fields if not isinstance(f, AutoField) and not f in obj._meta.parents.values()]) 
    return obj.__class__(**initial)

